﻿using System;
using System.Collections.Generic;
using System.Configuration;
using System.IO;
using System.Linq;
using System.Web;
using Lucene.Net.Analysis;
using Lucene.Net.Analysis.Standard;
using Lucene.Net.Documents;
using Lucene.Net.QueryParsers;
using Lucene.Net.Search;
using Lucene.Net.Store;
using Directory = Lucene.Net.Store.Directory;
using Version = Lucene.Net.Util.Version;
using Lucene.Net.Index;
using System.Xml;

namespace LuceneWrap.Lib
{
    public interface ILuceneSearcher
    {
        void Create();
        List<T> Search<T>(string property, string textsearch) where T : new();
        List<T> Search<T>(string textSearch) where T : new();
        List<T> GetResults<T>(string textSearch) where T : new();
    }

    public class LuceneSearcher : ILuceneSearcher
    {
        private readonly string _INDEX_FILEPATH = ConfigurationManager.AppSettings.Get("LuceneIndexFilePath");
        private Directory _luceneIndexDirectory = null;
        private IndexSearcher _indexSearcher = null;
        private QueryParser _queryParser = null;
        private Analyzer _analyzer = null;

        public IndexSearcher IndexSearcher
        {
            get
            {
                return _indexSearcher;
            }
        }

        //public LuceneSearcher()
        //{
        //}

        public int MaxDoc()
        {
            return _indexSearcher.MaxDoc();
        }

        public LuceneSearcher(string indexFilePath)
        {
            _INDEX_FILEPATH = indexFilePath;
            Create();
        }

        public void Create()
        {
            _luceneIndexDirectory = FSDirectory.Open(new DirectoryInfo(_INDEX_FILEPATH));
            _analyzer = new WhitespaceAnalyzer(); // new StandardAnalyzer();
            _indexSearcher = new IndexSearcher(_luceneIndexDirectory);
        }

        public void Finalize()
        {
            _luceneIndexDirectory.Close();
            _indexSearcher.Close();
            _analyzer.Close();
        }

        public List<T> Search<T>(string property, string textsearch) where T : new()
        {
            property = property.ToLower();
            //textsearch = textsearch.ToLower();
            _queryParser = new QueryParser(property, _analyzer);
            var result = GetResults<T>(textsearch);

            return result;
        }

        public List<T> Search<T>(string textSearch) where T : new()
        {
            return GetResults<T>(textSearch);
        }


        public List<T> SearchByPrimaryKeyField<T>(string fieldName, string fieldValue) where T : new()
        {
            TermQuery tq = new TermQuery(new Term(fieldName.ToLower(), fieldValue.ToLower()));
            BooleanQuery bq = new BooleanQuery();
            bq.Add(tq, BooleanClause.Occur.SHOULD);
            TopScoreDocCollector collector = TopScoreDocCollector.create(10, true);

            Hits hits = _indexSearcher.Search(bq);
            int resultsCount = hits.Length();

            List<T> results = new List<T>();

            for (int i = 0; i < resultsCount; i++)
            {
                Document doc = hits.Doc(i);
                var obj = LuceneReflection.GetObjFromDocument<T>(doc);
                results.Add(obj);
            }

            return results;
        }

        public List<T> GetResults<T>(string textSearch, int pageNumber, int rowsPerPage) where T : new()
        {
            //textSearch = textSearch.ToLower();

            List<T> results = new List<T>();

            if (_queryParser == null)
                _queryParser = new QueryParser("id", _analyzer);

            Query query;

            if (!string.IsNullOrEmpty(textSearch))
            {
                query = _queryParser.Parse(textSearch);

                string finalQuery = string.Format("({0}) AND {1}:\"{2}\"", query.ToString(), CommonConstants.ClrObjectTypeName, new T().GetType().FullName.ToLower());
                query = _queryParser.Parse(finalQuery);

            }
            else
            {
                string finalQuery = string.Format("{0}:\"{1}\"", CommonConstants.ClrObjectTypeName, new T().GetType().FullName.ToLower());
                query = _queryParser.Parse(finalQuery);

            }

            //Do the search
            //Hits hits = _indexSearcher.Search(query);
            //int resultsCount = hits.Length();

            //for (int i = 0; i < resultsCount; i++)
            //{
            //    Document doc = hits.Doc(i);
            //    var obj = LuceneReflection.GetObjFromDocument<T>(doc);
            //    results.Add(obj);
            //}


            //Using TopDocs

            // int page = 2; //starting from 0

            TopDocs td = _indexSearcher.Search(query, (pageNumber + 1) * rowsPerPage);

            for (int i = pageNumber * rowsPerPage; i < (pageNumber + 1) * rowsPerPage && i < td.scoreDocs.Length; i++)
            {
                Lucene.Net.Documents.Document doc = _indexSearcher.GetIndexReader().Document(td.scoreDocs[i].doc);

                var obj = LuceneReflection.GetObjFromDocument<T>(doc);
                results.Add(obj);
            }

            return results;
        }

        public List<T> GetResults<T>(string textSearch, int pageNumber, int rowsPerPage, out int totalRecords) where T : new()
        {
            return GetResultsAndSortByLatest<T>(textSearch, pageNumber, rowsPerPage, out totalRecords, false);
        }

        public List<T> GetResultsAndSortByLatest<T>(string textSearch, int pageNumber, int rowsPerPage, out int totalRecords, bool orderByLatest) where T : new()
        {
            //textSearch = textSearch.ToLower();

            List<T> results = new List<T>();

            if (_queryParser == null)
                _queryParser = new QueryParser("id", _analyzer);

            Query query;

            if (!string.IsNullOrEmpty(textSearch))
            {
                query = _queryParser.Parse(textSearch);

                string finalQuery = string.Format("({0}) AND {1}:\"{2}\"", query.ToString(), CommonConstants.ClrObjectTypeName, new T().GetType().FullName.ToLower());
                query = _queryParser.Parse(finalQuery);

            }
            else
            {
                string finalQuery = string.Format("{0}:\"{1}\"", CommonConstants.ClrObjectTypeName, new T().GetType().FullName.ToLower());
                query = _queryParser.Parse(finalQuery);

            }

            //Do the search

            Hits hits = _indexSearcher.Search(query, new Sort(new SortField("nid", SortField.LONG, true)));
            totalRecords = hits.Length();

            if (totalRecords == 0)
                return results;


            TopDocs td = null;

            if (orderByLatest == true)
            {
                td = _indexSearcher.Search(query, null, totalRecords, new Sort(new SortField("nid", SortField.LONG, false)));
            }
            else
            {
                td = _indexSearcher.Search(query, totalRecords);
            }

            totalRecords = td.totalHits;





            if (totalRecords < rowsPerPage)
            {
                for (int i = 0; i < totalRecords; i++)
                {
                    Lucene.Net.Documents.Document doc = _indexSearcher.GetIndexReader().Document(td.scoreDocs[i].doc);

                    var obj = LuceneReflection.GetObjFromDocument<T>(doc);
                    results.Add(obj);
                }
            }
            else
            {
                for (int i = (totalRecords - pageNumber * rowsPerPage)-1; i > ((totalRecords-1) - (pageNumber + 1) * rowsPerPage) && i >=0; --i)
                {
                    Lucene.Net.Documents.Document doc = _indexSearcher.GetIndexReader().Document(td.scoreDocs[i].doc);

                    var obj = LuceneReflection.GetObjFromDocument<T>(doc);
                    results.Add(obj);
                }
            }

            return results;
        }

        public List<T> TestNumericSearch<T>(long minValue, long maxValue, int pageNumber, int rowsPerPage, out int totalRecords) where T : new()
        {
            //textSearch = textSearch.ToLower();

            List<T> results = new List<T>();

            if (_queryParser == null)
                _queryParser = new QueryParser("id", _analyzer);

            Query query;

            NumericRangeQuery nQuery = NumericRangeQuery.NewLongRange("nid", minValue, maxValue, true, true);
            query = nQuery;

            //if (!string.IsNullOrEmpty(textSearch))
            //{
            //    query = _queryParser.Parse(textSearch);

            //    string finalQuery = string.Format("({0}) AND {1}:\"{2}\"", query.ToString(), CommonConstants.ClrObjectTypeName, new T().GetType().FullName.ToLower());
            //    query = _queryParser.Parse(finalQuery);

            //}
            //else
            //{
            //    string finalQuery = string.Format("{0}:\"{1}\"", CommonConstants.ClrObjectTypeName, new T().GetType().FullName.ToLower());
            //    query = _queryParser.Parse(finalQuery);

            //}

            //Do the search
            Hits hits = _indexSearcher.Search(query, new Sort(new SortField("nid", SortField.LONG, true)));
            totalRecords = hits.Length();


            //TopDocs matches = _indexSearcher.Search(query, null, 30, new Sort(new SortField("nid", SortField.INT, true)));

            if (totalRecords == 0)
                return results;


            //TopDocs td = _indexSearcher.Search(query, (pageNumber + 1) * rowsPerPage);

            //for (int i = pageNumber * rowsPerPage; i < (pageNumber + 1) * rowsPerPage && i < td.scoreDocs.Length; i++)
            //{
            //    Lucene.Net.Documents.Document doc = _indexSearcher.GetIndexReader().Document(td.scoreDocs[i].doc);

            //    var obj = LuceneReflection.GetObjFromDocument<T>(doc);
            //    results.Add(obj);
            //}


            TopDocs td = _indexSearcher.Search(query, null, totalRecords, new Sort(new SortField("nid", SortField.LONG, false)));

            totalRecords = td.totalHits;


            if (totalRecords < rowsPerPage)
            {
                for (int i = 0; i < totalRecords; i++)
                {
                    Lucene.Net.Documents.Document doc = _indexSearcher.GetIndexReader().Document(td.scoreDocs[i].doc);

                    var obj = LuceneReflection.GetObjFromDocument<T>(doc);
                    results.Add(obj);
                }
            }
            else
            {
                for (int i = (totalRecords - pageNumber * rowsPerPage) - 1; i > (totalRecords - (pageNumber + 1) * rowsPerPage) && i > 1; i--)
                {
                    Lucene.Net.Documents.Document doc = _indexSearcher.GetIndexReader().Document(td.scoreDocs[i].doc);

                    var obj = LuceneReflection.GetObjFromDocument<T>(doc);
                    results.Add(obj);
                }
            }

            return results;
        }

        public List<T> GetResults<T>(string textSearch) where T : new()
        {

            //textSearch = textSearch.ToLower();

            List<T> results = new List<T>();

            if (_queryParser == null)
                _queryParser = new QueryParser("id", _analyzer);

            Query query;

            if (!string.IsNullOrEmpty(textSearch))
            {
                query = _queryParser.Parse(textSearch);

                string finalQuery = string.Format("({0}) AND {1}:\"{2}\"", query.ToString(), CommonConstants.ClrObjectTypeName, new T().GetType().FullName.ToLower());
                query = _queryParser.Parse(finalQuery);

            }
            else
            {
                string finalQuery = string.Format("{0}:\"{1}\"", CommonConstants.ClrObjectTypeName, new T().GetType().FullName.ToLower());
                query = _queryParser.Parse(finalQuery);

            }
            //Do the search
            Hits hits = _indexSearcher.Search(query);
            int resultsCount = hits.Length();

            for (int i = 0; i < resultsCount; i++)
            {
                Document doc = hits.Doc(i);
                var obj = LuceneReflection.GetObjFromDocument<T>(doc);
                results.Add(obj);
            }

            return results;
        }


        public T GetFristDoc<T>() where T : new()
        {
            Document doc = _indexSearcher.Doc(0);

            var obj = LuceneReflection.GetObjFromDocument<T>(doc);

            return obj;
        }

        public List<T> GetAll<T>() where T : new()
        {
            return GetItemsByIndexRange<T>(0, _indexSearcher.MaxDoc());
        }

        public List<T> GetItemsByIndexRange<T>(int startIndex, int length) where T : new()
        {

            if (startIndex <= 0)
            {
                startIndex = 0;
            }

            int currentIdentityValue = _indexSearcher.MaxDoc();

            if (currentIdentityValue == 0)
            {
                return new List<T>();
            }
            else
            {
                if (startIndex > currentIdentityValue)
                    return new List<T>();

                if (currentIdentityValue < (length + startIndex))
                {
                    length = currentIdentityValue - startIndex;
                }

                List<T> resultList = new List<T>();
                for (int i = startIndex; i < startIndex + length; i++)
                {

                    Document doc = _indexSearcher.Doc(i);

                    var obj = LuceneReflection.GetObjFromDocument<T>(doc);

                    resultList.Add(obj);
                }

                return resultList;

            }

        }


        public static XmlDocument GetSearchResult(string Indexpath, string[] querys, string[] fields, BooleanClause.Occur[] clauses, string[] sortingfields)
        {
            /* How to use this fuction.
             * Ref: http://blog.logiclabz.com/c/search-lucene-index-in-net-c-with-sorting-options.aspx


            string[] querys = { "dominos pizza" };
            string[] fields = { "title" };
            Lucene.Net.Search.BooleanClause.Occur[] clauses = { Lucene.Net.Search.BooleanClause.Occur.MUST };
            string[] sortby = { "id" };

            XmlDocument xDoc = GetSearchResult(@"C:\luceneindexpath\", querys, fields, clauses, sortby);

            */

            //XMLDocument is created to return output records
            XmlDocument xResDoc = new XmlDocument();
            xResDoc.LoadXml("<SEARCHRESULTS></SEARCHRESULTS>");
            try
            {
                // This block will parse the given queries 
                // (i.e) would remove characters other than a-z and 0-9
                for (int i = 0; i < querys.Length; i++)
                {
                    querys[i] = querys[i].ToLower();
                    querys[i] = System.Text.RegularExpressions.Regex.Replace(querys[i], "[^a-z0-9]", " ");
                    while (querys[i].Contains("  "))
                        querys[i] = querys[i].Replace("  ", " ");
                }


                if (System.IO.Directory.Exists(Indexpath))
                {
                    // Open's Already created Lucene Index for searching
                    Lucene.Net.Index.IndexReader idxReader = Lucene.Net.Index.IndexReader.Open(Indexpath);
                    Searcher searcher = new IndexSearcher(idxReader);

                    //Creates a Lucene Query with given search inputs
                    Query qry = MultiFieldQueryParser.Parse(querys, fields, clauses, new WhitespaceAnalyzer());
                    ScoreDoc[] hits;

                    //Assigns No. of Records to be fetched 
                    //here idxReader max records is used 
                    //can be changed to get specific top records
                    int top = idxReader.MaxDoc();

                    //To get output with specific sort
                    if (sortingfields.Length > 0)
                    {
                        TopFieldDocCollector collector = new TopFieldDocCollector(idxReader, new Sort(sortingfields), top);
                        searcher.Search(qry, collector);
                        hits = collector.TopDocs().scoreDocs;
                    }
                    else //To get output without specific sort
                    {
                        TopDocCollector collector = new TopDocCollector(top);
                        searcher.Search(qry, collector);
                        hits = collector.TopDocs().scoreDocs;
                    }

                    //Loops through the output records and creates an XMLElement for XML Output
                    for (int i = 0; i < hits.Length; i++)
                    {
                        int docId = hits[i].doc;
                        Document doc = searcher.Doc(docId);
                        XmlElement xEle = xResDoc.CreateElement("SEARCHRESULT");
                        foreach (Field fl in doc.GetFields())
                        {
                            xEle.SetAttribute(fl.Name(), doc.Get(fl.Name()));
                        }
                        xResDoc.DocumentElement.AppendChild(xEle);
                    }
                    searcher.Close();
                    idxReader.Close();
                }
            }
            catch (Exception ex)
            {
                //Print Error Message
            }
            return xResDoc;
        }



    }


}